using System;
using System.Data;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;

namespace Dmoz.Importer
{
    /// <summary>
    /// In order to be able to Bulk copy XML data without having to load the whole document in memory,
    /// We must implement the IDataReader interface on top of an XmlTextReader class.
    /// </summary>
    public sealed class XmlImportDataReader : XmlTextReader, IDataReader, IDisposable
    {
        private DataTableReader _dataReader;

        private DataSet _dataSet;
        private DataTable _dataTable;

        /// <summary>
        /// Initializes a new instance of the <see cref="XmlImportDataReader"/> class.
        /// </summary>
        /// <param name="FullOutputPath">The full output path.</param>
        public XmlImportDataReader(string FullOutputPath) : base(FullOutputPath)
        {
        }

        /// <summary>
        /// Gets or sets the reader data table.
        /// </summary>
        /// <value>The reader data table.</value>
        public DataTable ReaderDataTable
        {
            get
            {
                if (_dataSet == null)
                {
                    _dataSet = new DataSet();
                    _dataSet.Locale = CultureInfo.InvariantCulture;
                    _dataSet.ReadXmlSchema("Import.xsd");
                    _dataTable = _dataSet.Tables["Import"];
                }
                return _dataTable;
            }
            set { _dataTable = value; }
        }

        /// <summary>
        /// Gets the data reader.
        /// </summary>
        /// <value>The data reader.</value>
        public DataTableReader DataReader
        {
            get
            {
                if (_dataReader == null)
                    _dataReader = ReaderDataTable.CreateDataReader();
                return _dataReader;
            }
        }

        #region IDataReader Members

        /// <summary>
        /// Reads the next node from the stream.
        /// </summary>
        /// <returns>
        /// true if the next node was read successfully; false if there are no more nodes to read.
        /// </returns>
        /// <exception cref="T:System.Xml.XmlException">An error occurred while parsing the XML. </exception>
        public override bool Read()
        {
            bool Result = false;
            StringBuilder sb = new StringBuilder();
            StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture);
            XmlTextWriter xw = new XmlTextWriter(sw);

            xw.WriteStartElement("Imports");

            while (!Result)
            {
                if (!base.Read())
                    break;

                switch (NodeType)
                {
                    case XmlNodeType.EndElement:
                        xw.WriteEndElement();
                        if (String.Compare(Name, "Import", false, CultureInfo.InvariantCulture) == 0)
                            Result = true;
                        break;
                    case XmlNodeType.Text:
                        xw.WriteValue(base.Value);
                        break;
                    case XmlNodeType.Element:
                        switch (Name)
                        {
                            case "Import":
                                ReaderDataTable.Clear();
                                xw.WriteStartElement("Import");
                                break;
                            case "idRow":
                                xw.WriteStartElement("idRow");
                                break;
                            case "catid":
                                xw.WriteStartElement("catid");
                                break;
                            case "resourcetype":
                                xw.WriteStartElement("resourcetype");
                                break;
                            case "value":
                                xw.WriteStartElement("value");
                                break;
                            case "title":
                                xw.WriteStartElement("title");
                                break;
                            case "language":
                                xw.WriteStartElement("language");
                                break;
                        }
                        if (IsEmptyElement) xw.WriteEndElement();
                        break;
                }
            }

            if (!EOF)
                xw.WriteEndElement();

            StringReader sr = new StringReader(sb.ToString());
            ReaderDataTable.ReadXml(sr);
            _dataReader = ReaderDataTable.CreateDataReader();
            _dataReader.Read();

            return Result;
        }

        ///<summary>
        ///Returns a <see cref="T:System.Data.DataTable"></see> that describes the column metadata of the <see cref="T:System.Data.IDataReader"></see>.
        ///</summary>
        ///
        ///<returns>
        ///A <see cref="T:System.Data.DataTable"></see> that describes the column metadata.
        ///</returns>
        ///
        ///<exception cref="T:System.InvalidOperationException">The <see cref="T:System.Data.IDataReader"></see> is closed. </exception><filterpriority>2</filterpriority>
        DataTable IDataReader.GetSchemaTable()
        {
            return DataReader.GetSchemaTable();
        }

        ///<summary>
        ///Advances the data reader to the next result, when reading the results of batch SQL statements.
        ///</summary>
        ///
        ///<returns>
        ///true if there are more rows; otherwise, false.
        ///</returns>
        ///<filterpriority>2</filterpriority>
        bool IDataReader.NextResult()
        {
            return Read();
        }

        ///<summary>
        ///Gets a value indicating whether the data reader is closed.
        ///</summary>
        ///
        ///<returns>
        ///true if the data reader is closed; otherwise, false.
        ///</returns>
        ///<filterpriority>2</filterpriority>
        bool IDataReader.IsClosed
        {
            get { return DataReader.IsClosed; }
        }

        ///<summary>
        ///Gets the number of rows changed, inserted, or deleted by execution of the SQL statement.
        ///</summary>
        ///
        ///<returns>
        ///The number of rows changed, inserted, or deleted; 0 if no rows were affected or the statement failed; and -1 for SELECT statements.
        ///</returns>
        ///<filterpriority>2</filterpriority>
        int IDataReader.RecordsAffected
        {
            get { return DataReader.RecordsAffected; }
        }

        ///<summary>
        ///Gets the name for the field to find.
        ///</summary>
        ///
        ///<returns>
        ///The name of the field or the empty string (""), if there is no value to return.
        ///</returns>
        ///
        ///<param name="i">The index of the field to find. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        string IDataRecord.GetName(int i)
        {
            return DataReader.GetName(i);
        }

        ///<summary>
        ///Gets the data type information for the specified field.
        ///</summary>
        ///
        ///<returns>
        ///The data type information for the specified field.
        ///</returns>
        ///
        ///<param name="i">The index of the field to find. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        string IDataRecord.GetDataTypeName(int i)
        {
            return DataReader.GetDataTypeName(i);
        }

        ///<summary>
        ///Gets the <see cref="T:System.Type"></see> information corresponding to the type of <see cref="T:System.Object"></see> that would be returned from <see cref="M:System.Data.IDataRecord.GetValue(System.Int32)"></see>.
        ///</summary>
        ///
        ///<returns>
        ///The <see cref="T:System.Type"></see> information corresponding to the type of <see cref="T:System.Object"></see> that would be returned from <see cref="M:System.Data.IDataRecord.GetValue(System.Int32)"></see>.
        ///</returns>
        ///
        ///<param name="i">The index of the field to find. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        Type IDataRecord.GetFieldType(int i)
        {
            return DataReader.GetFieldType(i);
        }

        ///<summary>
        ///Return the value of the specified field.
        ///</summary>
        ///
        ///<returns>
        ///The <see cref="T:System.Object"></see> which will contain the field value upon return.
        ///</returns>
        ///
        ///<param name="i">The index of the field to find. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        object IDataRecord.GetValue(int i)
        {
            return DataReader.GetValue(i);
        }

        ///<summary>
        ///Gets all the attribute fields in the collection for the current record.
        ///</summary>
        ///
        ///<returns>
        ///The number of instances of <see cref="T:System.Object"></see> in the array.
        ///</returns>
        ///
        ///<param name="values">An array of <see cref="T:System.Object"></see> to copy the attribute fields into. </param><filterpriority>2</filterpriority>
        int IDataRecord.GetValues(object[] values)
        {
            return DataReader.GetValues(values);
        }

        ///<summary>
        ///Return the index of the named field.
        ///</summary>
        ///
        ///<returns>
        ///The index of the named field.
        ///</returns>
        ///
        ///<param name="name">The name of the field to find. </param><filterpriority>2</filterpriority>
        int IDataRecord.GetOrdinal(string name)
        {
            return DataReader.GetOrdinal(name);
        }

        ///<summary>
        ///Gets the value of the specified column as a Boolean.
        ///</summary>
        ///
        ///<returns>
        ///The value of the column.
        ///</returns>
        ///
        ///<param name="i">The zero-based column ordinal. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        bool IDataRecord.GetBoolean(int i)
        {
            return DataReader.GetBoolean(i);
        }

        ///<summary>
        ///Gets the 8-bit unsigned integer value of the specified column.
        ///</summary>
        ///
        ///<returns>
        ///The 8-bit unsigned integer value of the specified column.
        ///</returns>
        ///
        ///<param name="i">The zero-based column ordinal. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        byte IDataRecord.GetByte(int i)
        {
            return DataReader.GetByte(i);
        }

        ///<summary>
        ///Reads a stream of bytes from the specified column offset into the buffer as an array, starting at the given buffer offset.
        ///</summary>
        ///
        ///<returns>
        ///The actual number of bytes read.
        ///</returns>
        ///
        ///<param name="buffer">The buffer into which to read the stream of bytes. </param>
        ///<param name="bufferoffset">The index for buffer to start the read operation. </param>
        ///<param name="fieldOffset">The index within the field from which to start the read operation. </param>
        ///<param name="i">The zero-based column ordinal. </param>
        ///<param name="length">The number of bytes to read. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        long IDataRecord.GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
        {
            return DataReader.GetBytes(i, fieldOffset, buffer, bufferoffset, length);
        }

        ///<summary>
        ///Gets the character value of the specified column.
        ///</summary>
        ///
        ///<returns>
        ///The character value of the specified column.
        ///</returns>
        ///
        ///<param name="i">The zero-based column ordinal. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        char IDataRecord.GetChar(int i)
        {
            return DataReader.GetChar(i);
        }

        ///<summary>
        ///Reads a stream of characters from the specified column offset into the buffer as an array, starting at the given buffer offset.
        ///</summary>
        ///
        ///<returns>
        ///The actual number of characters read.
        ///</returns>
        ///
        ///<param name="fieldoffset">The index within the row from which to start the read operation. </param>
        ///<param name="buffer">The buffer into which to read the stream of bytes. </param>
        ///<param name="bufferoffset">The index for buffer to start the read operation. </param>
        ///<param name="i">The zero-based column ordinal. </param>
        ///<param name="length">The number of bytes to read. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        long IDataRecord.GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
        {
            return DataReader.GetChars(i, fieldoffset, buffer, bufferoffset, length);
        }

        ///<summary>
        ///Returns the GUID value of the specified field.
        ///</summary>
        ///
        ///<returns>
        ///The GUID value of the specified field.
        ///</returns>
        ///
        ///<param name="i">The index of the field to find. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        Guid IDataRecord.GetGuid(int i)
        {
            return DataReader.GetGuid(i);
        }

        ///<summary>
        ///Gets the 16-bit signed integer value of the specified field.
        ///</summary>
        ///
        ///<returns>
        ///The 16-bit signed integer value of the specified field.
        ///</returns>
        ///
        ///<param name="i">The index of the field to find. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        short IDataRecord.GetInt16(int i)
        {
            return DataReader.GetInt16(i);
        }

        ///<summary>
        ///Gets the 32-bit signed integer value of the specified field.
        ///</summary>
        ///
        ///<returns>
        ///The 32-bit signed integer value of the specified field.
        ///</returns>
        ///
        ///<param name="i">The index of the field to find. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        int IDataRecord.GetInt32(int i)
        {
            return DataReader.GetInt32(i);
        }

        ///<summary>
        ///Gets the 64-bit signed integer value of the specified field.
        ///</summary>
        ///
        ///<returns>
        ///The 64-bit signed integer value of the specified field.
        ///</returns>
        ///
        ///<param name="i">The index of the field to find. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        long IDataRecord.GetInt64(int i)
        {
            return DataReader.GetInt64(i);
        }

        ///<summary>
        ///Gets the single-precision floating point number of the specified field.
        ///</summary>
        ///
        ///<returns>
        ///The single-precision floating point number of the specified field.
        ///</returns>
        ///
        ///<param name="i">The index of the field to find. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        float IDataRecord.GetFloat(int i)
        {
            return DataReader.GetFloat(i);
        }

        ///<summary>
        ///Gets the double-precision floating point number of the specified field.
        ///</summary>
        ///
        ///<returns>
        ///The double-precision floating point number of the specified field.
        ///</returns>
        ///
        ///<param name="i">The index of the field to find. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        double IDataRecord.GetDouble(int i)
        {
            return DataReader.GetDouble(i);
        }

        ///<summary>
        ///Gets the string value of the specified field.
        ///</summary>
        ///
        ///<returns>
        ///The string value of the specified field.
        ///</returns>
        ///
        ///<param name="i">The index of the field to find. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        string IDataRecord.GetString(int i)
        {
            return DataReader.GetString(i);
        }

        ///<summary>
        ///Gets the fixed-position numeric value of the specified field.
        ///</summary>
        ///
        ///<returns>
        ///The fixed-position numeric value of the specified field.
        ///</returns>
        ///
        ///<param name="i">The index of the field to find. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        decimal IDataRecord.GetDecimal(int i)
        {
            return DataReader.GetDecimal(i);
        }

        ///<summary>
        ///Gets the date and time data value of the specified field.
        ///</summary>
        ///
        ///<returns>
        ///The date and time data value of the spcified field.
        ///</returns>
        ///
        ///<param name="i">The index of the field to find. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        DateTime IDataRecord.GetDateTime(int i)
        {
            return DataReader.GetDateTime(i);
        }

        ///<summary>
        ///Gets an <see cref="T:System.Data.IDataReader"></see> to be used when the field points to more remote structured data.
        ///</summary>
        ///
        ///<returns>
        ///An <see cref="T:System.Data.IDataReader"></see> to be used when the field points to more remote structured data.
        ///</returns>
        ///
        ///<param name="i">The index of the field to find. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        IDataReader IDataRecord.GetData(int i)
        {
            return DataReader.GetData(i);
        }

        ///<summary>
        ///Return whether the specified field is set to null.
        ///</summary>
        ///
        ///<returns>
        ///true if the specified field is set to null. Otherwise, false.
        ///</returns>
        ///
        ///<param name="i">The index of the field to find. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        bool IDataRecord.IsDBNull(int i)
        {
            return DataReader.IsDBNull(i);
        }

        ///<summary>
        ///Gets the number of columns in the current row.
        ///</summary>
        ///
        ///<returns>
        ///When not positioned in a valid recordset, 0; otherwise the number of columns in the current record. The default is -1.
        ///</returns>
        ///<filterpriority>2</filterpriority>
        int IDataRecord.FieldCount
        {
            get { return DataReader.FieldCount; }
        }


        ///<summary>
        ///Gets the column located at the specified index.
        ///</summary>
        ///
        ///<returns>
        ///The column located at the specified index as an <see cref="T:System.Object"></see>.
        ///</returns>
        ///
        ///<param name="i">The index of the column to get. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"></see>. </exception><filterpriority>2</filterpriority>
        object IDataRecord.this[int i]
        {
            get { return DataReader[i]; }
        }

        ///<summary>
        ///Gets the column with the specified name.
        ///</summary>
        ///
        ///<returns>
        ///The column with the specified name as an <see cref="T:System.Object"></see>.
        ///</returns>
        ///
        ///<param name="name">The name of the column to find. </param>
        ///<exception cref="T:System.IndexOutOfRangeException">No column with the specified name was found. </exception><filterpriority>2</filterpriority>
        object IDataRecord.this[string name]
        {
            get { return DataReader[name]; }
        }

        

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        // NOTE: Leave out the finalizer altogether if this class doesn't 
        // own unmanaged resources itself, but leave the other methods
        // exactly as they are. 
        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="XmlImportDataReader"/> is reclaimed by garbage collection.
        /// </summary>
        ~XmlImportDataReader()
        {
            // Finalizer calls Dispose(false)
            Dispose(false);
        }

        /// <summary>
        /// Releases the unmanaged resources used by the <see cref="T:System.Xml.XmlReader"></see> and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            try
            {
                if (disposing)
                {
                    // free managed resources
                    if (_dataReader != null)
                    {
                        _dataReader.Dispose();
                        _dataReader = null;
                    }
                    if (_dataTable != null)
                    {
                        _dataTable.Dispose();
                        _dataTable = null;
                    }
                    if (_dataSet != null)
                    {
                        _dataSet.Dispose();
                        _dataSet = null;
                    }
                }
            }
            finally
            {
                base.Dispose(disposing);
            }
        }

        #endregion
    }
}